package AddonDiff;

use strict;
use warnings;

use File::Spec;
use Algorithm::Diff qw(compact_diff);
use Carp;

use Database;
use Options;

BEGIN {
    use Exporter ();
    our ( $VERSION, @ISA, @EXPORT, @EXPORT_OK, %EXPORT_TAGS );

    $VERSION = sprintf( "%d", q$Revision$ =~ m{\d+} );
    @ISA     = qw(Exporter);
    @EXPORT  = qw(&diff_only_in
        &diff_contents
        &diff_toc);
    %EXPORT_TAGS = ();
    @EXPORT_OK   = qw();
} ## end BEGIN

use constant {
    ColorReset    => $out_is_term ? "\e[" . "39m" : "",
    ColorAdd      => $out_is_term ? "\e[" . "96m" : "",
    ColorSubtract => $out_is_term ? "\e[" . "31m" : "",
    ColorMeta     => $out_is_term ? "\e[" . "95m" : "",
    ColorPlain    => $out_is_term ? "\e[" . "90m" : "",
};

sub diff_only_in($ );
sub diff_contents($$$$ );
sub diff_toc($$$$;$ );

sub diff_only_in($ ) {
    Carp::cluck if ref $_[ 0 ];
    my ( $volume, $dir, $file ) = File::Spec->splitpath( shift );
    return "Only in " . File::Spec->catdir( $volume, $dir ) . ": " . $file . "\n";
}

sub ignore_white_space {
    my $value = shift;
    return $value if $value eq "\n";
    $value =~ s{\s+}{ }g;
    return $value;
} ## end sub ignore_white_space

sub diff_contents($$$$ ) {
    my ( $old_contents, $old_name, $new_contents, $new_name ) = @_;

    my $context_before = 3;
    my $context_after  = 3;

    my @res = ();
    return @res if ( $old_contents eq $new_contents );
    if (   $old_contents =~ m{\0}
        || $new_contents =~ m{\0} )
    {
        push @res, "Binary files " . $old_name . " and " . $new_name . " differ\n";
        return @res;
    } ## end if ( $old_contents =~ ...)
    my @old_lines = split_lines( $old_contents );
    my @new_lines = split_lines( $new_contents );

    my @diff = compact_diff( \@old_lines, \@new_lines, \&ignore_white_space );
    my $diff_size = scalar @diff;
    my ( $last_old, $last_new, $res_header );
    for ( my $chunk = 2 ; $chunk + 3 < $diff_size ; $chunk += 4 ) {
        my $old_start = $diff[ $chunk ];
        my $new_start = $diff[ $chunk + 1 ];
        if ( !defined $last_old ) {

            # We do not have an active diff going
            my $old_prior = $diff[ $chunk - 2 ];
            my $new_prior = $diff[ $chunk - 1 ];
            unless ( @res ) {
                ### Intial file header
                push @res,
                    (
                    ColorSubtract . "--- " . $old_name . ColorReset . "\n",
                    ColorAdd . "+++ " . $new_name . ColorReset . "\n"
                    );
            } ## end unless ( @res )
            $res_header = scalar @res;
            push @res, "@@ ?? ?? @@\n";
            if ( $old_prior + $context_before < $old_start ) {
                $old_prior = $old_start - $context_before;
                $new_prior = $new_start - $context_before;
            }
            ( $last_old, $last_new ) = ( $old_prior, $new_prior );
            while ( $old_prior < $old_start ) {
                push @res, ColorPlain . " " . $old_lines[ $old_prior ] . ColorReset . "\n";
                ++$old_prior;
            }
        } ## end if ( !defined $last_old)
        my $old_end = $diff[ $chunk + 2 ];
        my $new_end = $diff[ $chunk + 3 ];
        if (   ( $old_start != $old_end )
            && ( $new_start != $new_end )
            && ( $old_end < $old_start + 25 )
            && ( $new_end < $new_start + 25 )
            && $out_is_term )
        {
            my @old_chunk = ();
            my @new_chunk = ();
            while ( $old_start < $old_end ) {
                push @old_chunk, split( m{(\s+|[[:punct:]])}, $old_lines[ $old_start ] ), "\n";
                ++$old_start;
            }
            while ( $new_start < $new_end ) {
                push @new_chunk, split( m{(\s+|[[:punct:]])}, $new_lines[ $new_start ] ), "\n";
                ++$new_start;
            }
            my @diff2 = compact_diff( \@old_chunk, \@new_chunk, \&ignore_white_space );
            my $diff2_size = scalar @diff2;
            for ( my $group = 0 ; $group < 2 ; ++$group ) {
                my $keep        = 1;
                my $line        = "";
                my $line_start  = ( $group ? "+" : "-" );
                my $color_group = ( $group ? ColorAdd : ColorSubtract );
                my $array       = ( $group ? \@new_chunk : \@old_chunk );
                for ( my $index = 2 + $group ; $index < $diff2_size ; $index += 2 ) {
                    my $start     = $diff2[ $index - 2 ];
                    my $end       = $diff2[ $index ];
                    my $color_def = ( $keep ? ColorPlain : $color_group );
                    my $color     = $color_def;
                    for ( ; $start < $end ; ++$start ) {
                        my $piece = $array->[ $start ];
                        if ( $piece eq "\n" ) {
                            push @res, $color_group . $line_start . $line . ColorReset . "\n";
                            $line  = "";
                            $color = $color_def;
                        }
                        else {
                            $line .= $color . $piece;
                            $color = "";
                        }
                    } ## end for ( ; $start < $end ;...)
                    $keep = !$keep;
                } ## end for ( my $index = 2 + $group...)
            } ## end for ( my $group = 0 ; $group...)
        } ## end if ( ( $old_start != $old_end...))
        else {
            while ( $old_start < $old_end ) {
                push @res, ColorSubtract . "-" . $old_lines[ $old_start ] . ColorReset . "\n";
                ++$old_start;
            }
            while ( $new_start < $new_end ) {
                push @res, ColorAdd . "+" . $new_lines[ $new_start ] . ColorReset . "\n";
                ++$new_start;
            }
        } ## end else [ if ( ( $old_start != $old_end...))]
        my $old_next = $diff[ $chunk + 4 ];
        my $new_next = $diff[ $chunk + 5 ];
        my $at_end;
        if ( defined $old_next ) {
            my $avail_context = $old_next - $old_end;
            $at_end        = 1 if ( $diff_size < $chunk + 8 );
            $avail_context = 0 if ( $avail_context < 0 );
            my $context_allowed = $context_after + ( $at_end ? 0 : $context_before );
            if ( $avail_context > $context_allowed ) {
                $avail_context = $context_after;
                $at_end        = 1;
            }
            $old_next = $old_end + $avail_context;
            $new_next = $new_end + $avail_context;
            while ( $old_end < $old_next ) {
                push @res, ColorPlain . " " . $old_lines[ $old_end ] . ColorReset . "\n";
                ++$old_end;
            }
        } ## end if ( defined $old_next)
        else {
            $at_end   = 1;
            $old_next = $old_end;
            $new_next = $new_end;
        }
        if ( $at_end ) {
            $res[ $res_header ] = ( ColorMeta . "@@ -" . $last_old . "," . ( $old_next - $last_old ) . " +" . $last_new . "," . ( $new_next - $last_new ) . " @@" . ColorReset . "\n" );
            undef $last_old;
            undef $last_new;
        }
    } ## end for ( my $chunk = 2 ; $chunk...)
    return @res;
} ## end sub diff_contents($$$$ )

sub diff_toc($$$$;$ ) {
    my ( $old, $old_path, $new, $new_path, $check_only ) = @_;

    my $old_text = $old->extract;
    my $new_text = $new->extract;

    $old_path = File::Spec->catfile( $old_path, $old->get_name );
    $new_path = File::Spec->catfile( $new_path, $new->get_name );
    if ( $old_text eq $new_text ) {
        return ( () );
    }
    elsif ( $check_only ) {
        return ( ( "Files " . $old_path . " and " . $new_path . " differ\n" ) );
    }
    else {
        return diff_contents( $old_text, $old_path, $new_text, $new_path );
    }
} ## end sub diff_toc($$$$;$ )

1;
